![HaskellとcatsのApplicative Functorについて考えてみた](https://devio2023-media.developers.io/wp-content/uploads/2019/06/scala_eye_catch.png)
HaskellとcatsのApplicative Functorについて考えてみた
はじめに
cats.Applicativeの使い道がいまいちわからないのですごいH本を参考に考えてみました。
Applicative
まずcatsでのApplicativeの定義は下記です(抜粋)。
trait Applicative[F[_]] extends Functor[F] { def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] def pure[A](a: A): F[A] def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa) }
ap
についてすごいH本では下記のような例が紹介されています。<*>
によってApplicativeを扱っていない関数をApplicative値に適用できます(<*>
がcatsでいうap
)
Prelude Control.Applicative> (pure (+)) <*> Just 1 <*> Just 2 Just 3
catsでも同様に<*>
はap
のエイリアスとして定義されていて、同等の使い方ができます。
@ import cats.Applicative import cats.Applicative @ Applicative[Option].pure { a:Int => b:Int => a+b } <*> 1.some <*> 2.some res6: Option[Int] = Some(3)
さて、HaskellのApplicativeは以下の演算子<$>
をエクスポートしています。
(<$>) :: (Functor f) => (a -> b) -> f a > f b f <$> x = fmap f x
これを使うと先ほどの例は以下のようになります。
Prelude Control.Applicative> (+) <$> Just 1 <*> Just 2 Just 3
部分適用することもできます。
Prelude Control.Applicative> :t ((+) <$> Just 1) ((+) <$> Just 1) :: Num a => Maybe (a -> a)
catsではmapN
がこれに相当するように見えます。これを使うと<$>
のように部分適用もできます。しかし実際のところmapN
はSemigroupal#mapN(n = {2....N})
のエイリアスで、型も異なっています。
@ import cats.implicits._ import cats.implicits._ //mapN @ (1.some, 2.some).mapN(_+_) res1: Option[Int] = Some(3) //map2 @ Applicative[Option].map2(1.some, 2.some)(_+_) res5: Option[Int] = Some(3) //部分適用 @ (1.some, _:Option[Int]).mapN(_+_) res17: Option[Int] => Option[Int] = ammonite.$sess.cmd17$$$Lambda$1873/1950478035@7432cb37 @ (Applicative[Option].map2(1.some, _:Option[Int])(_+_)) res19: Option[Int] => Option[Int] = ammonite.$sess.cmd19$$$Lambda$1896/649325384@77f21c0a
そこでmap2
、map3
の定義を見るとSemigroupal#product
が使われているのがわかります。
def map2[F[_], A0, A1, Z](f0:F[A0], f1:F[A1])(f: (A0, A1) => Z)(implicit semigroupal: Semigroupal[F], functor: Functor[F]): F[Z] = functor.map(semigroupal.product(f0, f1)) { case (a0, a1) => f(a0, a1) } def map3[F[_], A0, A1, A2, Z](f0:F[A0], f1:F[A1], f2:F[A2])(f: (A0, A1, A2) => Z)(implicit semigroupal: Semigroupal[F], functor: Functor[F]): F[Z] = functor.map(semigroupal.product(f0, semigroupal.product(f1, f2))) { case (a0, (a1, a2)) => f(a0, a1, a2) }
<$>
とmapN
は似ていますがやはり別物に見えます。
ここでproduct
はSemigroupal
において以下のように定義されています。
@typeclass trait Semigroupal[F[_]] { def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] }
product
をfmap
とap
で実装してみます。
implicit def semigroupal[F[_] : Applicative]: Semigroupal[F] = new Semigroupal[F] { override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = Functor[F].fmap(fa) { a: A => b: B => (a, b) } <*> fb }
これは タプルを扱っているものの<$>
と<*>
を組み合わせて使ったときと同じです。
まとめ
Applicativeを使うことでApplicativeを扱わない関数をApplicative値に適用できることがわかりました。